package model;

public class Perspectiva {
    
    // VRP
    double vrpX, vrpY, vrpZ;
    
    // Ponto Observado P
    double pX, pY, pZ;
    
    // Centro do plano de projeção
    double cX, cY, cZ;
    
    // Window
    double largura, altura;
    
    // viewport
    double uMin, vMin, uMax, vMax;
    
    // FD
    double dp = 50;
    
    // vetor N
    double nX, nY, nZ;
    
    // norma de N
    double normaN, normaV, normaU;
    
    // vetor Y
    double yX = 0, yY = 1, yZ = 0;
    
    // produto escalar (y*n)
    double prodEscalarY;
    
    // vetor v
    double vX, vY, vZ;
    
    // vetor v
    double uX, uY, uZ;
    
    // Matriz T
    double matrizT[][];
    
    // matriz r
    double matrizR[][];
    
    // matriz RT
    double matrizRT[][];
    
    // vetor VRP e Vetor centro projeção
    double vetorVRP[][], vetorCentroProj[][];
    
    // distancia entre vrp e o plano de projeção(dp)
    double distanciaVrpPlano;
    
    // matriz perspectiva
    double matrizPers[][];
    
    // matriz composta SRC e Projeção
    double matrizComps[][];
    
    // vetores Window
    double vetorMin[][], vetorMax[][];
    
    // matriz PERS to SRT
    double matrizPersSrt[][];
    
    // pontos
    double pontos[][];
    
    // pontos em tela
    double pontosTela[][];
    
    public Perspectiva(Camera cam, double uMax, double vMax, double[][] pontos){
        
        this.vrpX = cam.getVx();
        this.vrpY = cam.getVy();
        this.vrpZ = cam.getVz();
        
        this.pX = cam.getPx();
        this.pY = cam.getPy();
        this.pZ = cam.getPz();
        
        this.uMax = uMax;
        this.vMax = vMax;
        this.uMin = 0;
        this.vMin = 0;
        
        this.largura = cam.getLargura();
        this.altura = cam.getAltura();
        
        this.dp = cam.getDp();
        
        this.pontos = pontos;
        
        this.yX = cam.getYx();
        this.yY = cam.getYy();
        this.yZ = cam.getYz();
        
        executaPerspectiva();
    }
    
    @SuppressWarnings("empty-statement")
    public void executaPerspectiva(){
        // calcular centro do plano de projeção
        cX = vrpX+(pX-vrpX)*dp/100;
        cY = vrpY+(pY-vrpY)*dp/100;
        cZ = vrpZ+(pZ-vrpZ)*dp/100;
        
        // vetor N
        nX = vrpX - pX;
        nY = vrpY - pY;
        nZ = vrpZ - pZ;
        
        // normaliza n
        normaN = Math.sqrt(Math.pow(nX, 2) + Math.pow(nY, 2) + Math.pow(nZ, 2));
        nX /= normaN;
        nY /= normaN;
        nZ /= normaN;
        
        // produto escalar (y*n)
        prodEscalarY = (nX*yX) + (nY*yY) + (nZ*yZ);
        
        // vetor V
        vX = yX-prodEscalarY*nX;
        vY = yY-prodEscalarY*nY;
        vZ = yZ-prodEscalarY*nZ;
        
        // normaliza v
        normaV = Math.sqrt(Math.pow(vX, 2) + Math.pow(vY, 2) + Math.pow(vZ, 2));
        vX /= normaV;
        vY /= normaV;
        vZ /= normaV;
        
        // vetor U
        uX = vY*nZ-(vZ*nY);
        uY = vZ*nX-(vX*nZ);
        uZ = vX*nY-(vY*nX);
        
        // monta matriz T
        matrizT = new double[][]{{1,0,0,-vrpX},{0,1,0,-vrpY},{0,0,1,-vrpZ},{0,0,0,1}};
        
        // monta matriz R
        matrizR = new double[][]{{uX,uY,uZ,0},{vX,vY,vZ,0},{nX,nY,nZ,0},{0,0,0,1}};
        
//        for (int i = 0; i < matrizT.length; i++) {
//            for (int j = 0; j < matrizT[0].length; j++) {
//                System.out.print(matrizT[i][j] + " ");
//            }
//            System.out.println("");
//        }
        
        // multiplica as matriz RT
        matrizRT = multMatrix(matrizR, matrizT);
        
        // seta em vetor o VRP e o centro de projeção
        vetorVRP = new double[][]{{vrpX},{vrpY},{vrpZ},{1}};
        vetorCentroProj = new double[][]{{cX},{cY},{cZ},{1}};
        
        // multiplica RT por VRP e Centro do plano de projeção
        vetorVRP = multMatrix(matrizRT, vetorVRP);
        vetorCentroProj = multMatrix(matrizRT, vetorCentroProj);
        
        // distancia VRP e o plnao de projeção
        distanciaVrpPlano = Math.sqrt(Math.pow(vetorCentroProj[0][0]-vetorVRP[0][0], 2)
                +Math.pow(vetorCentroProj[1][0]-vetorVRP[1][0], 2)
                +Math.pow(vetorCentroProj[2][0]-vetorVRP[2][0], 2));
        
        // matriz perspectiva
        matrizPers = new double[][]{{1,0,0,0},
                                    {0,1,0,0},
                                    {0,0,-(vetorCentroProj[2][0]/distanciaVrpPlano),vetorCentroProj[2][0]*(vetorVRP[2][0]/distanciaVrpPlano)},
                                    {0,0,-(1/distanciaVrpPlano),vetorVRP[2][0]/distanciaVrpPlano}
        };
    
        // matriz composta pers e projeção
        matrizComps = multMatrix(matrizPers, matrizRT);
        
        // vetores min e max do window
        vetorMin = new double[][]{{vetorCentroProj[0][0]-largura},{vetorCentroProj[1][0]-altura},{vetorCentroProj[2][0],1}};
        vetorMax = new double[][]{{vetorCentroProj[0][0]+largura},{vetorCentroProj[1][0]+altura},{vetorCentroProj[2][0],1}};
        
        // matriz PERS to SRT
        matrizPersSrt = new double[][]{{(uMax-uMin)/(vetorMax[0][0]-vetorMin[0][0]),0, -vetorMin[0][0]*((uMax-uMin)/(vetorMax[0][0]-vetorMin[0][0]))+uMin},
                                        {0,(vMax-vMin)/(vetorMax[1][0]-vetorMin[1][0]),-vetorMin[1][0]*((vMax-vMin)/(vetorMax[1][0]-vetorMin[1][0]))+vMin},
                                        {0,0,1}
        };
        
        // aplica matriz composta SRC e Projeção nos pontos
        pontos = multMatrix(matrizComps, pontos);
        
        // divide o componente W dos pontos
        pontosTela = new double[3][pontos[0].length];
        for (int i = 0; i < pontosTela[0].length; i++) {
            pontosTela[0][i] = pontos[0][i]/pontos[3][i];
            pontosTela[1][i] = pontos[1][i]/pontos[3][i];
            pontosTela[2][i] = 1;
        }
        
        // aplica matriz PERS to SRT nos pontos de tela
        pontosTela = multMatrix(matrizPersSrt, pontosTela);
    }
    
    
    public double[][] getPontosTela(){
        return pontosTela;
    }
    
    public double[][] multMatrix(double m1[][], double m2[][]) {
        double[][] m3 = new double[m2.length][m2[0].length];
        double temp1 = 0;
        for (int i = 0; i < m1.length; i++) {
            for (int j = 0; j < m2[0].length; j++) {
                for (int k = 0; k < m2.length; k++) {
                    temp1 += m1[i][k] * m2[k][j];
                }
                m3[i][j] = temp1;
                temp1 = 0;
            }
        }
        return m3;
    }
}
